package com.drg.center.dao;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSONObject;
import com.drg.center.entity.PatientCaseEsPO;
import com.drg.center.entity.result.*;
import com.drg.center.utils.BusinessDateUtil;
import com.drg.center.utils.EsResultResolveUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.guaike.core.entity.exception.ApiException;
import com.guaike.core.utils.BigDecimalUtil;
import com.guaike.core.utils.CompareUtil;
import com.guaike.core.utils.LocalDateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedReverseNested;
import org.elasticsearch.search.aggregations.bucket.nested.ReverseNestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.existsQuery;

/**
 * @Author: zhangxilin
 * @Date: 2025/1/5
 */
@Slf4j
@Repository
public class EsDrgAngleDAO {

    @Autowired
    private ElasticsearchOperations elasticsearchOperations;


    /**
     * 病组疑难病例分布图-rw分布(DSL-109)
     * DSL-019
     *
     * @param searchDrgCodeList
     * @param startDate
     * @param endDate
     * @return
     */
    public DifficultCaseDistributeVO getDifficultCaseDistribute(List<String> searchDrgCodeList, LocalDate startDate, LocalDate endDate) {
        // 检索条件
        BoolQueryBuilder queryBuilder = boolQuery();
        queryBuilder.must(QueryBuilders.termQuery("hasOut", true));
        if (!CollectionUtils.isEmpty(searchDrgCodeList)) {
            queryBuilder.must(QueryBuilders.termsQuery("drgCode", searchDrgCodeList));
        }
        if (startDate != null) {
            queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").gte(LocalDateUtil.getTimestamp(startDate)));
        }
        if (endDate != null) {
            queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").lt(LocalDateUtil.getTimestamp(endDate.plusDays(1))));
        }


        RangeAggregationBuilder rwRangeAgg = AggregationBuilders.range("case-by-rw-range")
                .field("rw")
                .addRange("0-0.5", 0, 0.5)
                .addRange("0.5-1", 0.5, 1)
                .addRange("1-1.5", 1, 1.5)
                .addRange("1.5-2", 1.5, 2.0)
                .addRange("2-3", 2.0, 3)
                .addUnboundedFrom("大于3", 3)
                .subAggregation(AggregationBuilders.count("caseCount").field("caseCode"));


        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .addAggregation(rwRangeAgg)
                .addAggregation(AggregationBuilders.terms("case_by_dept_agg").field("drgCode").size(5)
                        .subAggregation(rwRangeAgg)
                        .subAggregation(AggregationBuilders.topHits("drgName").size(1)
                                .fetchSource(new String[]{"drgName"}, null))
                )
                .withPageable(PageRequest.of(0, 1))
                .build();

        Aggregations aggregations = elasticsearchOperations.search(searchQuery, PatientCaseEsPO.class).getAggregations();

        // 解析 case-by-rw-range 聚合
        Range rwRanges = aggregations.get("case-by-rw-range");

        // 解析 case_by_dept_agg 聚合
        Terms deptBuckets = aggregations.get("case_by_dept_agg");

//        List<GroupDifficultCaseDistributeVO> deptCaseDistributeList = Lists.newArrayList();
        List<String> dataXList = Lists.newArrayList();
        List<String> segmentList = Lists.newArrayList();

        Map<String, List<Object>> dataMap = Maps.newHashMap();
        for (Terms.Bucket entry : deptBuckets.getBuckets()) {

            //科室名称
            TopHits departNameHits = (TopHits) entry.getAggregations().get("drgName");
            String groupName = EsResultResolveUtil.getName(departNameHits, 0, "drgName");

            dataXList.add(groupName);
            Range rwRangesByDept = entry.getAggregations().get("case-by-rw-range");
            setCaseDistributeList(rwRangesByDept, dataMap, segmentList);


        }


        DifficultCaseDistributeVO vo = new DifficultCaseDistributeVO();
        vo.setCaseDistributeList(getCaseDistributeList(rwRanges));
        vo.setComplexBarVO(getComplexBarVO(dataXList, segmentList, dataMap));

        log.info("【EsDrgAngleDAO.DifficultCaseDistributeVO】vo:{}", JSONObject.toJSONString(vo));
        return vo;
    }

    private ComplexBarVO getComplexBarVO(List<String> dataX, List<String> segmentList, Map<String, List<Object>> dataMap){
        List<List<Object>> dataList = Lists.newArrayList();
        for(String sg: segmentList){
            List<Object> list = dataMap.get(sg);
            if(!CollectionUtils.isEmpty(list)){
                dataList.add(list);
            }
        }
        ComplexBarVO barVO = new ComplexBarVO();
        barVO.setDataList(dataList);
        barVO.setDataX(dataX);
        barVO.setSegments(segmentList);
        return barVO;
    }


    /**
     * 病组盈亏分析(DSL-110)
     *
     * @param year
     * @param drgCodeList
     * @return
     */
    public ChartLineVO getDrgProfitTrend(Integer year, List<String> drgCodeList) {
        if (year == null) {
            throw new ApiException("请选择年份");
        }

        List<String> dataX = BusinessDateUtil.getMonthList(year);

        // 检索条件
        BoolQueryBuilder queryBuilder = boolQuery();
        queryBuilder.must(QueryBuilders.termQuery("hasOut", true));


        LocalDateTime firstDayOfYear = LocalDateUtil.getFirstDayOfYear(year);
        LocalDateTime lastDayOfYear = LocalDateUtil.getLastDayOfYear(year);
        queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").gte(LocalDateUtil.getTimestamp(firstDayOfYear)));
        queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").lte(LocalDateUtil.getTimestamp(lastDayOfYear)));


        if (!CollectionUtils.isEmpty(drgCodeList)) {
            queryBuilder.must(QueryBuilders.termsQuery("drgCode", drgCodeList));
        }


        DateHistogramAggregationBuilder dateHistogramAgg = AggregationBuilders.dateHistogram("monthly_profit")
                .field("outHospitalDate")
                .calendarInterval(DateHistogramInterval.MONTH)
                .format("yyyy-MM")
                .timeZone(ZoneId.of("+08:00"))
                .subAggregation(AggregationBuilders.sum("total_profit").field("profit").format("0.00"));

        TermsAggregationBuilder doctorsTerms = AggregationBuilders.terms("drgAgg")
                .field("drgCode")
                .size(5)
                .subAggregation(AggregationBuilders.topHits("drgName").size(1)
                        .fetchSource(new String[]{"drgName"}, null))
                .subAggregation(dateHistogramAgg);


        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .addAggregation(doctorsTerms)
                .withPageable(PageRequest.of(0, 1))
                .build();

        Aggregations aggregations = elasticsearchOperations.search(searchQuery, PatientCaseEsPO.class).getAggregations();
        Terms doctorTerms = aggregations.get("drgAgg");

        List<ChartLineSeriesVO> series = new ArrayList<>();

        for (Terms.Bucket entry : doctorTerms.getBuckets()) {
            String doctorCode = entry.getKeyAsString();

            TopHits topHits = entry.getAggregations().get("drgName");
            SearchHit[] hits = topHits.getHits().getHits();
            String groupName = null;
            if (!ArrayUtil.isEmpty(hits)) {
                Map<String, Object> sourceAsMap = hits[0].getSourceAsMap();
                groupName = (String) sourceAsMap.get("drgName");
            }

            Histogram perMonthAgg = entry.getAggregations().get("monthly_profit");
            Map<String, Object> dataMap = getMonthlyMap(perMonthAgg);
            List<Object> dataYList = Lists.newArrayList();
            for (String month : dataX) {
                Object value = dataMap.get(month);
                dataYList.add(ObjectUtils.isEmpty(value) ? null : value);
            }

            ChartLineSeriesVO seriesVO = new ChartLineSeriesVO();
            seriesVO.setSeriesName(groupName);
            seriesVO.setCode(doctorCode);
            seriesVO.setDataList(dataYList);
            series.add(seriesVO);
        }

        ChartLineVO vo = new ChartLineVO();
        vo.setDataX(dataX);
        vo.setSeries(series);

        log.info("【EsDrgAngleDAO.getDrgProfitTrend】 vo: {}", JSONObject.toJSONString(vo));
        return vo;
    }

    /**
     * 病组例均费用分布(DSL-111)
     *
     * @param year
     * @param drgCodeList
     * @return
     */
    public ChartLineVO getDrgAvgCostTrend(Integer year, List<String> drgCodeList) {
        if (year == null) {
            throw new ApiException("请选择年份");
        }

        // 检索条件
        BoolQueryBuilder queryBuilder = boolQuery();
        queryBuilder.must(QueryBuilders.termQuery("hasOut", true));


        LocalDateTime firstDayOfYear = LocalDateUtil.getFirstDayOfYear(year);
        LocalDateTime lastDayOfYear = LocalDateUtil.getLastDayOfYear(year);
        queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").gte(LocalDateUtil.getTimestamp(firstDayOfYear)));
        queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").lte(LocalDateUtil.getTimestamp(lastDayOfYear)));


        if (!CollectionUtils.isEmpty(drgCodeList)) {
            queryBuilder.must(QueryBuilders.termsQuery("drgCode", drgCodeList));
        }


        DateHistogramAggregationBuilder dateHistogramAgg = AggregationBuilders.dateHistogram("monthly_profit")
                .field("outHospitalDate")
                .calendarInterval(DateHistogramInterval.MONTH)
                .format("yyyy-MM")
                .timeZone(ZoneId.of("+08:00"))
                .subAggregation(AggregationBuilders.avg("medicineCost").field("medicineCost").format("0.00"))
                .subAggregation(AggregationBuilders.avg("consumablesCost").field("consumablesCost").format("0.00"))
                .subAggregation(AggregationBuilders.avg("otherCost").field("otherCost").format("0.00"));


        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .addAggregation(dateHistogramAgg)
                .withPageable(PageRequest.of(0, 1))
                .build();

        Aggregations aggregations = elasticsearchOperations.search(searchQuery, PatientCaseEsPO.class).getAggregations();
        Histogram perMonthAgg = aggregations.get("monthly_profit");


        List<ChartLineSeriesVO> series = Lists.newArrayList();

        List<String> monthList = Lists.newArrayList();
        List<Object> medicineCostList = Lists.newArrayList();
        List<Object> consumablesCostList = Lists.newArrayList();
        List<Object> otherCostList = Lists.newArrayList();


        List<? extends Histogram.Bucket> buckets = perMonthAgg.getBuckets();
        for (Histogram.Bucket bucket : buckets) {
            String month = bucket.getKeyAsString();
            monthList.add(month);
            ParsedAvg medicineCostAvg = bucket.getAggregations().get("medicineCost");
            ParsedAvg consumablesCostAvg = bucket.getAggregations().get("consumablesCost");
            ParsedAvg otherCostAvg = bucket.getAggregations().get("otherCost");

            BigDecimal medicineCost = BigDecimalUtil.parseBigDecimal(medicineCostAvg.getValueAsString());
            BigDecimal consumablesCost = BigDecimalUtil.parseBigDecimal(consumablesCostAvg.getValueAsString());
            String otherCostStr = otherCostAvg.getValueAsString();
            if (CompareUtil.isEqual("Infinity", otherCostStr)) {
                otherCostStr = null;
            }

            BigDecimal otherCost = BigDecimalUtil.parseBigDecimal(otherCostStr);

            medicineCostList.add(medicineCost == null ? BigDecimal.ZERO : medicineCost);
            consumablesCostList.add(consumablesCost == null ? BigDecimal.ZERO : consumablesCost);
            otherCostList.add(otherCost == null ? BigDecimal.ZERO : otherCost);
        }
        series.add(new ChartLineSeriesVO("次均药品费用", medicineCostList));
        series.add(new ChartLineSeriesVO("次均耗材费用", consumablesCostList));
        series.add(new ChartLineSeriesVO("次均其他费用", otherCostList));

        ChartLineVO vo = new ChartLineVO();
        vo.setDataX(monthList);
        vo.setSeries(series);
        log.info("【EsDrgAngleDAO.getDrgAvgCostTrend】 vo: {}", JSONObject.toJSONString(vo));
        return vo;
    }

    /**
     * 病组盈亏项目统计--本条产品设计逻辑混乱-应付演示(DSL-112)
     *
     * @param searchDepartCode
     * @param searchDrgCode
     * @param startDate
     * @param endDate
     */
    public List<DrgProfitLossProjectTotalPageVO> getDrgProjectStt(String searchDepartCode, String searchDrgCode, LocalDate startDate, LocalDate endDate) {
        // 检索条件
        BoolQueryBuilder queryBuilder = boolQuery();
        queryBuilder.must(QueryBuilders.termQuery("hasOut", true));
        if (StringUtils.isNotBlank(searchDepartCode)) {
            queryBuilder.must(QueryBuilders.termQuery("outDepartCode", searchDepartCode));
        }
        if (StringUtils.isNotBlank(searchDrgCode)) {
            queryBuilder.must(QueryBuilders.termQuery("drgCode", searchDrgCode));
        }
        if (startDate != null) {
            queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").gte(LocalDateUtil.getTimestamp(startDate)));
        }
        if (endDate != null) {
            queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").lt(LocalDateUtil.getTimestamp(endDate.plusDays(1))));
        }


        // 构建聚合部分
        TermsAggregationBuilder termsAggBuilder = AggregationBuilders.terms("groupAgg")
                .field("adviceVOS.adviceContentCode.keyword")
                .size(100);

        NestedAggregationBuilder nestedAggBuilder = AggregationBuilders.nested("projectAgg", "adviceVOS");

        ValueCountAggregationBuilder caseCountAgg = AggregationBuilders.count("caseCount").field("caseCode");

        ReverseNestedAggregationBuilder reverseNestedAgg = AggregationBuilders.reverseNested("reverse_nested_agg");


        FilterAggregationBuilder usePorjectOfProfitCaseCountFilter = AggregationBuilders.filter("usePorjectOfProfitCaseCount", QueryBuilders.rangeQuery("profit").gte(0))
                .subAggregation(caseCountAgg);
        FilterAggregationBuilder usePorjectOfLossCaseCountFilter = AggregationBuilders.filter("usePorjectOfLossCaseCount", QueryBuilders.rangeQuery("profit").lt(0))
                .subAggregation(caseCountAgg);
        FilterAggregationBuilder overCaseCountFilter = AggregationBuilders.filter("overCaseCount", QueryBuilders.rangeQuery("overExpectCost").gt(0))
                .subAggregation(caseCountAgg);
        TopHitsAggregationBuilder topHitsAgg = AggregationBuilders.topHits("groupName")
                .size(5)
                .fetchSource(new String[]{"outDepartName", "doctorName", "drgName", "lossReason", "overEffect"}, null);

        TopHitsAggregationBuilder topHitsProjectNameAgg = AggregationBuilders.topHits("groupName")
                .size(1)
                .fetchSource(new String[]{"adviceVOS.adviceContent"}, null);

        termsAggBuilder.subAggregation(topHitsProjectNameAgg).subAggregation(reverseNestedAgg
                .subAggregation(usePorjectOfProfitCaseCountFilter)
                .subAggregation(usePorjectOfLossCaseCountFilter)
                .subAggregation(overCaseCountFilter)
                .subAggregation(AggregationBuilders.count("usePorjectCaseCount").field("caseCode"))
                .subAggregation(topHitsAgg));

        nestedAggBuilder.subAggregation(termsAggBuilder);


        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .addAggregation(nestedAggBuilder)
                .addAggregation(caseCountAgg)
                .withPageable(PageRequest.of(0, 1))
                .build();

        Aggregations aggregations = elasticsearchOperations.search(searchQuery, PatientCaseEsPO.class).getAggregations();

        ValueCount caseCount = aggregations.get("caseCount");

        //总病历数
        Integer totalCaseCount = (int) caseCount.getValue();

        // 获取 nested 聚合 "projectAgg"
        ParsedNested projectAgg = aggregations.get("projectAgg");
        // 从 nested 聚合中获取 terms 聚合 "groupAgg"
        Terms groupAgg = projectAgg.getAggregations().get("groupAgg");

        List<DrgProfitLossProjectTotalPageVO> list = new ArrayList<>();

        for (Terms.Bucket bucket : groupAgg.getBuckets()) {
            DrgProfitLossProjectTotalPageVO vo = new DrgProfitLossProjectTotalPageVO();
            //项目名称
            TopHits projectNameHits = (TopHits) bucket.getAggregations().get("groupName");
            String projectName = getName(projectNameHits, 0, "adviceContent");

            ParsedReverseNested parsedReverseNested = bucket.getAggregations().get("reverse_nested_agg");
            if (parsedReverseNested == null) {
                continue;
            }

            //使用该项目病例数
            ParsedValueCount usePorjectCaseCountValueCount = parsedReverseNested.getAggregations().get("usePorjectCaseCount");
            Integer usePorjectCaseCount = (int) usePorjectCaseCountValueCount.getValue();


            //盈利病例中使用该项目病例数
            Integer usePorjectOfProfitCaseCount = getCount(parsedReverseNested.getAggregations(), "usePorjectOfProfitCaseCount");

            //亏损病例中使用该项目病例数
            Integer usePorjectOfLossCaseCount = getCount(parsedReverseNested.getAggregations(), "usePorjectOfLossCaseCount");

            //超支病例数
            Integer overCaseCount = getCount(parsedReverseNested.getAggregations(), "overCaseCount");

            //名称
            TopHits groupNameHits = (TopHits) parsedReverseNested.getAggregations().get("groupName");
            //科室
            String outDepartName = getName(groupNameHits, 0, "outDepartName");
            //医生
            String doctorName = getName(groupNameHits, 1, "doctorName");
            //DRG病组
            String drgName = getName(groupNameHits, 2, "drgName");
            //亏损因素
            String lossReason = getName(groupNameHits, 3, "lossReason");
            //超支影响
            String overEffect = getName(groupNameHits, 4, "overEffect");
            vo.setDepartName(outDepartName);
            vo.setDoctorName(doctorName);
            vo.setChsDrgName(drgName);
            vo.setLossReason(lossReason);
            vo.setOverEffect(overEffect);
            vo.setProjectName(projectName);
            vo.setOverMix(BigDecimalUtil.formatBigDecimal(new BigDecimal(overCaseCount).divide(new BigDecimal(totalCaseCount), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)) + "%");
            vo.setUseProjectCaseCount(usePorjectCaseCount);
            vo.setUseProjectCaseMix(BigDecimalUtil.formatBigDecimal(new BigDecimal(usePorjectCaseCount).divide(new BigDecimal(totalCaseCount), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)) + "%");
            vo.setUseProjectOfLossCaseMix(BigDecimalUtil.formatBigDecimal(new BigDecimal(usePorjectOfLossCaseCount).divide(new BigDecimal(totalCaseCount), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)) + "%");
            vo.setUseProjectOfProfitCaseMix(BigDecimalUtil.formatBigDecimal(new BigDecimal(usePorjectOfProfitCaseCount).divide(new BigDecimal(totalCaseCount), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)) + "%");
            list.add(vo);
        }
        return list;
    }

    private String getName(TopHits departNameHits, int index, String fieldName) {
        if (ArrayUtil.isEmpty(departNameHits.getHits().getHits())) {
            return null;
        }
        return (String) departNameHits.getHits().getHits()[index].getSourceAsMap().get(fieldName);
    }

    /**
     * 病例复杂程度分析(DSL-113)
     *
     * @param searchDepartCode
     * @param startDate
     * @param endDate
     */
    public List<CaseComplexityAnalysisPageVO> getComplexity(String searchDepartCode, LocalDate startDate, LocalDate endDate) {
        // 检索条件
        BoolQueryBuilder queryBuilder = boolQuery();
        queryBuilder.must(QueryBuilders.termQuery("hasOut", true));
        if (StringUtils.isNotBlank(searchDepartCode)) {
            queryBuilder.must(QueryBuilders.termQuery("outDepartCode", searchDepartCode));
        }
        if (startDate != null) {
            queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").gte(LocalDateUtil.getTimestamp(startDate)));
        }
        if (endDate != null) {
            queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").lt(LocalDateUtil.getTimestamp(endDate.plusDays(1))));
        }

        TermsAggregationBuilder doctorsTerms = AggregationBuilders.terms("departments")
                .field("outDepartCode")
                .size(10000)
                .order(BucketOrder.aggregation("caseCount", false))
                .subAggregation(AggregationBuilders.topHits("departName").size(1)
                        .fetchSource(new String[]{"outDepartName"}, null))
                .subAggregation(AggregationBuilders.count("caseCount").field("caseCode"))
                .subAggregation(AggregationBuilders.cardinality("drgCount").field("drgCode"))


                .subAggregation(AggregationBuilders.filter("low-dealth-risk", QueryBuilders.termQuery("dealthRisk", 1))
                        .subAggregation(AggregationBuilders.count("caseCount").field("caseCode")))
                .subAggregation(AggregationBuilders.filter("middle-low-dealth-risk", QueryBuilders.termQuery("dealthRisk", 2))
                        .subAggregation(AggregationBuilders.count("caseCount").field("caseCode")))
                .subAggregation(AggregationBuilders.filter("unaccomp-case", QueryBuilders.termQuery("drgType", 1))
                        .subAggregation(AggregationBuilders.count("caseCount").field("caseCode")))
                .subAggregation(AggregationBuilders.filter("accomp-case", QueryBuilders.termQuery("drgType", 2))
                        .subAggregation(AggregationBuilders.count("caseCount").field("caseCode")))
                .subAggregation(AggregationBuilders.filter("serious-accomp-case", QueryBuilders.termQuery("drgType", 3))
                        .subAggregation(AggregationBuilders.count("caseCount").field("caseCode")))


                .subAggregation(AggregationBuilders.filter("caseInDrgCount",
                        boolQuery().must(existsQuery("drgCode"))
                                .mustNot(QueryBuilders.termQuery("drgCode", "0000")))
                );

        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withPageable(PageRequest.of(0, 1))
                .addAggregation(doctorsTerms)
                .build();

        Aggregations aggregations = elasticsearchOperations.search(searchQuery, PatientCaseEsPO.class).getAggregations();

        Terms doctorTerms = aggregations.get("departments");
        List<CaseComplexityAnalysisPageVO> list = new ArrayList<>();
        for (Terms.Bucket entry : doctorTerms.getBuckets()) {
            CaseComplexityAnalysisPageVO vo = new CaseComplexityAnalysisPageVO();
            String departCode = entry.getKeyAsString();

            //科室名称
            TopHits topHits = entry.getAggregations().get("departName");
            SearchHit[] hits = topHits.getHits().getHits();
            String departName = null;
            if (!ArrayUtil.isEmpty(hits)) {
                Map<String, Object> sourceAsMap = hits[0].getSourceAsMap();
                departName = (String) sourceAsMap.get("outDepartName");
            }

            // 病例数
            ValueCount caseCountValueCount = entry.getAggregations().get("caseCount");
            Integer caseCount = (int) caseCountValueCount.getValue();

            // 入组病例数
            ParsedFilter caseInDrgCountFilter = entry.getAggregations().get("caseInDrgCount");
            Integer caseInDrgCount = (int) caseInDrgCountFilter.getDocCount();


            // 病组数
            Cardinality drgCountCardinality = entry.getAggregations().get("drgCount");
            Integer drgCount = (int) drgCountCardinality.getValue();

            //低风险死亡
            Integer lowDeathCount = getCount(entry, "low-dealth-risk");

            //中低风险死亡
            Integer middleLowDeathCount = getCount(entry, "middle-low-dealth-risk");

            //不伴并发症与合并症病例数
            Integer unaccompCount = getCount(entry, "unaccomp-case");

            //伴并发症与合并症病例数
            Integer accompCount = getCount(entry, "accomp-case");

            //伴严重并发症与合并症病例数
            Integer seriousAccompCase = getCount(entry, "serious-accomp-case");
            vo.setDepartName(departName);
            vo.setCaseCount(caseCount);
            vo.setGroupCaseCount(caseInDrgCount);
            vo.setDiseaseGroup(drgCount);
            vo.setGroupRatio(BigDecimalUtil.formatBigDecimal(new BigDecimal(caseInDrgCount).divide(new BigDecimal(caseCount), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)) + "%");
            vo.setLowDieRatio(BigDecimalUtil.formatBigDecimal(new BigDecimal(lowDeathCount).divide(new BigDecimal(caseCount), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)) + "%");
            vo.setLowMiddleDieRatio(BigDecimalUtil.formatBigDecimal(new BigDecimal(middleLowDeathCount).divide(new BigDecimal(caseCount), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)) + "%");
            vo.setUnAccompCount(unaccompCount);
            vo.setUnAccompMix(BigDecimalUtil.formatBigDecimal(new BigDecimal(unaccompCount).divide(new BigDecimal(caseCount), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)) + "%");
            vo.setAccompCount(accompCount);
            vo.setAccompMix(BigDecimalUtil.formatBigDecimal(new BigDecimal(accompCount).divide(new BigDecimal(caseCount), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)) + "%");
            vo.setSeriousAccompCase(seriousAccompCase);
            vo.setSeriousAccompMix(BigDecimalUtil.formatBigDecimal(new BigDecimal(seriousAccompCase).divide(new BigDecimal(caseCount), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)) + "%");
            list.add(vo);
        }
        return list;
    }

    /**
     * 住院费用箱式分析(DSL-114)
     *
     * @param year
     * @return
     */
    public ChartLineVO getCostBoxData(Integer year) {
        if (year == null) {
            throw new ApiException("请选择年份");
        }

        // 检索条件
        BoolQueryBuilder queryBuilder = boolQuery();
        queryBuilder.must(QueryBuilders.termQuery("hasOut", true));


        LocalDateTime firstDayOfYear = LocalDateUtil.getFirstDayOfYear(year);
        LocalDateTime lastDayOfYear = LocalDateUtil.getLastDayOfYear(year);
        queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").gte(LocalDateUtil.getTimestamp(firstDayOfYear)));
        queryBuilder.must(QueryBuilders.rangeQuery("outHospitalDate").lte(LocalDateUtil.getTimestamp(lastDayOfYear)));


        DateHistogramAggregationBuilder dateHistogramAgg = AggregationBuilders.dateHistogram("monthly_profit")
                .field("outHospitalDate")
                .calendarInterval(DateHistogramInterval.MONTH)
                .format("yyyy-MM")
                .timeZone(ZoneId.of("+08:00"))
                .subAggregation(AggregationBuilders.avg("avgCost").field("totalCost").format("0.00"))
                .subAggregation(AggregationBuilders.min("minCost").field("totalCost").format("0.00"))
                .subAggregation(AggregationBuilders.max("maxCost").field("totalCost").format("0.00"))
                .subAggregation(AggregationBuilders.percentiles("25-perCost").field("totalCost").percentiles(25).keyed(false).format("0.00"))
                .subAggregation(AggregationBuilders.percentiles("50-perCost").field("totalCost").percentiles(50).keyed(false).format("0.00"))
                .subAggregation(AggregationBuilders.percentiles("75-perCost").field("totalCost").percentiles(75).keyed(false).format("0.00"));

        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .addAggregation(dateHistogramAgg)
                .withPageable(PageRequest.of(0, 1))
                .build();

        Aggregations aggregations = elasticsearchOperations.search(searchQuery, PatientCaseEsPO.class).getAggregations();
        Histogram perMonthAgg = aggregations.get("monthly_profit");


        List<ChartLineSeriesVO> series = Lists.newArrayList();

        List<String> monthList = BusinessDateUtil.getMonthList(year);
        List<Object> avgDataList = Lists.newArrayList();
        List<Object> boxDataList = Lists.newArrayList();


        Map<String, Object> avgDataMap = Maps.newHashMap();
        Map<String, Object> boxDataMap = Maps.newHashMap();

        List<? extends Histogram.Bucket> buckets = perMonthAgg.getBuckets();
        for (Histogram.Bucket bucket : buckets) {
            String month = bucket.getKeyAsString();

            Avg avgCostAgg = bucket.getAggregations().get("avgCost");
            Min minCostAgg = bucket.getAggregations().get("minCost");
            Max maxCostAgg = bucket.getAggregations().get("maxCost");

            Percentiles percentiles25Agg = bucket.getAggregations().get("25-perCost");
            Percentiles percentiles50Agg = bucket.getAggregations().get("50-perCost");
            Percentiles percentiles75Agg = bucket.getAggregations().get("75-perCost");

            String percentiles25 = percentiles25Agg.percentileAsString(25);
            String percentiles50 = percentiles50Agg.percentileAsString(50);
            String percentiles75 = percentiles75Agg.percentileAsString(75);

            avgDataMap.put(month, avgCostAgg.getValueAsString());
            List<String> boxDatas = ListUtil.toList(minCostAgg.getValueAsString(), percentiles25, percentiles50, percentiles75, maxCostAgg.getValueAsString());
            boxDataMap.put(month, boxDatas);
        }

        for(String month: monthList){
           Object avgValue = avgDataMap.get(month);
           Object boxValue = boxDataMap.get(month);
            avgDataList.add(ObjectUtils.isEmpty(avgValue)? null: avgValue);
            boxDataList.add(ObjectUtils.isEmpty(boxValue)? Lists.newArrayList(): boxValue);
        }


        series.add(new ChartLineSeriesVO("平均值", avgDataList));
        series.add(new ChartLineSeriesVO("箱式分析", boxDataList));

        ChartLineVO vo = new ChartLineVO();
        vo.setDataX(monthList);
        vo.setSeries(series);
        log.info("【EsDrgAngleDAO.getCostBoxData】 vo: {}", JSONObject.toJSONString(vo));
        return vo;
    }


    /**
     * DRG结算统计(DSL-120)
     * @param startDate
     * @param endDate
     * @return
     */
    public List<DrgSettleVO> getDrgSettleStt(LocalDate startDate, LocalDate endDate){

        // 检索条件
        BoolQueryBuilder queryBuilder = boolQuery();
        queryBuilder.must(QueryBuilders.termQuery("hasOut", true));

        if (startDate != null) {
            queryBuilder.must(QueryBuilders.rangeQuery("settleDate").gte(LocalDateUtil.getTimestamp(startDate)));
        }
        if (endDate != null) {
            queryBuilder.must(QueryBuilders.rangeQuery("settleDate").lt(LocalDateUtil.getTimestamp(endDate.plusDays(1))));
        }

        ValueCountAggregationBuilder caseCountAgg = AggregationBuilders.count("count").field("caseCode");


        // 分组一致病例数
        String drgEqualsScript = " if (doc['drgCode'].size() > 0 && doc['drgYbCode'].size() > 0) {\n" +
                "                    return doc['drgCode'].value == doc['drgYbCode'].value;\n" +
                "                  } else {\n" +
                "                    return false;\n" +
                "                  }";
        FilterAggregationBuilder drgCodeEqualsYbDrgCountAgg = AggregationBuilders.filter("drgCodeEqualsYbDrgCount",
                QueryBuilders.scriptQuery(
                        new Script(ScriptType.INLINE, "painless", drgEqualsScript, Collections.emptyMap())));

        String drgNotEqualsScript = " if (doc['drgCode'].size() > 0 && doc['drgYbCode'].size() > 0) {\n" +
                "                    return doc['drgCode'].value != doc['drgYbCode'].value;\n" +
                "                  } else {\n" +
                "                    return false;\n" +
                "                  }";
        FilterAggregationBuilder drgCodeNeEqualsYbDrgCountAgg = AggregationBuilders.filter("drgCodeNeEqualsYbDrgCount",
                QueryBuilders.scriptQuery(
                        new Script(ScriptType.INLINE, "painless",drgNotEqualsScript, Collections.emptyMap())));


        // 结算金额大于总成本计数聚合
        String settleMoneyGtTotalCostScript = "  if (doc['settleMoney'].size() > 0 && doc['totalCost'].size() > 0) {\n" +
                "                    def settleMoney = doc['settleMoney'].value;\n" +
                "                    def totalCost = doc['totalCost'].value;\n" +
                "                    return settleMoney > totalCost;\n" +
                "                  } else {\n" +
                "                    return false;\n" +
                "                  }";
        FilterAggregationBuilder settleMoneyGtTotalCostCountAgg = AggregationBuilders.filter("settleMoneyGtTotalCostCount",
                QueryBuilders.scriptQuery(
                        new Script(ScriptType.INLINE, "painless", settleMoneyGtTotalCostScript, Collections.emptyMap())));

        // 结算金额小于总成本计数聚合
        String settleMoneyLtTotalCostScript = " if (doc['settleMoney'].size() > 0 && doc['totalCost'].size() > 0) {\n" +
                "                    def settleMoney = doc['settleMoney'].value;\n" +
                "                    def totalCost = doc['totalCost'].value;\n" +
                "                    return settleMoney < totalCost;\n" +
                "                  } else {\n" +
                "                    return false;\n" +
                "                  }";
        FilterAggregationBuilder settleMoneyLtTotalCostCountAgg = AggregationBuilders.filter("settleMoneyLtTotalCostCount",
                QueryBuilders.scriptQuery(
                        new Script(ScriptType.INLINE, "painless",settleMoneyLtTotalCostScript, Collections.emptyMap())));



        DateHistogramAggregationBuilder dateHistogramAgg = AggregationBuilders.dateHistogram("monthly_profit")
                .field("outHospitalDate")
                .calendarInterval(DateHistogramInterval.MONTH)
                .format("yyyy-MM")
                .timeZone(ZoneId.of("+08:00"))
                .subAggregation(AggregationBuilders.filter("caseInDrgCount",
                        boolQuery().must(existsQuery("drgCode"))
                                .mustNot(QueryBuilders.termQuery("drgCode", "0000"))
                ).subAggregation(caseCountAgg))
                .subAggregation(AggregationBuilders.filter("caseInYbDrgCount",
                        boolQuery().must(existsQuery("drgYbCode"))
                                .mustNot(QueryBuilders.termQuery("drgYbCode", "0000"))
                ).subAggregation(caseCountAgg))
                .subAggregation(drgCodeEqualsYbDrgCountAgg.subAggregation(caseCountAgg))
                .subAggregation(drgCodeNeEqualsYbDrgCountAgg.subAggregation(caseCountAgg))
                .subAggregation(settleMoneyGtTotalCostCountAgg.subAggregation(caseCountAgg))
                .subAggregation(settleMoneyLtTotalCostCountAgg.subAggregation(caseCountAgg))
                .subAggregation(caseCountAgg);

        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .addAggregation(dateHistogramAgg)
                .withPageable(PageRequest.of(0, 1))
                .build();

        Aggregations aggregations = elasticsearchOperations.search(searchQuery, PatientCaseEsPO.class).getAggregations();
        Histogram perMonthAgg = aggregations.get("monthly_profit");

        List<DrgSettleVO> list = Lists.newArrayList();

        List<? extends Histogram.Bucket> buckets = perMonthAgg.getBuckets();
        for (Histogram.Bucket bucket : buckets) {
            DrgSettleVO vo = new DrgSettleVO();
            Aggregations entry =  bucket.getAggregations();
            //月份
            String month = bucket.getKeyAsString();
            vo.setMonth(month);

            //系统入组病例数
            Integer caseInDrgCount = EsResultResolveUtil.getFilterCount(entry, "caseInDrgCount", "count");
            vo.setSysInDrgCase(caseInDrgCount);

            //医保入组病例数
            Integer caseInYbDrgCount = EsResultResolveUtil.getFilterCount(entry, "caseInYbDrgCount", "count");
            vo.setYbInDrgCase(caseInYbDrgCount);

            //分组不一致病例数
            Integer drgCodeNeEqualsYbDrgCount = EsResultResolveUtil.getFilterCount(entry, "drgCodeNeEqualsYbDrgCount", "count");
            vo.setGroupNoSameCase(drgCodeNeEqualsYbDrgCount);

            //分组一致病例数
            Integer drgCodeEqualsYbDrgCount = EsResultResolveUtil.getFilterCount(entry, "drgCodeEqualsYbDrgCount", "count");
            vo.setGroupSameCase(drgCodeEqualsYbDrgCount);
            Integer totalCount = drgCodeNeEqualsYbDrgCount + drgCodeEqualsYbDrgCount;
            BigDecimal rate = NumberUtil.div(drgCodeEqualsYbDrgCount, totalCount).setScale(2, RoundingMode.HALF_UP);
            vo.setGroupSameRatio(BigDecimalUtil.formatBigDecimal(rate));

            //医保多出病例数
            Integer settleMoneyGtTotalCostCount = EsResultResolveUtil.getFilterCount(entry, "settleMoneyGtTotalCostCount", "count");
            vo.setYbOverCase(settleMoneyGtTotalCostCount);

            //系统多出病例数
            Integer settleMoneyLtTotalCostCount = EsResultResolveUtil.getFilterCount(entry, "settleMoneyLtTotalCostCount", "count");
            vo.setSysOverCase(settleMoneyLtTotalCostCount);
            list.add(vo);

        }
        log.info("【EsDrgAngleDAO.getDrgSettleStt】 list: {}", JSONObject.toJSONString(list));
        return list;
    }



    private Integer getCount(Terms.Bucket entry, String aggName) {
        ParsedFilter countFilter = entry.getAggregations().get(aggName);
        ValueCount valueCount = countFilter.getAggregations().get("caseCount");
        return (int) valueCount.getValue();
    }

    private Integer getCount(Aggregations aggregations, String aggName) {
        ParsedFilter countFilter = aggregations.get(aggName);
        ValueCount valueCount = countFilter.getAggregations().get("caseCount");
        return (int) valueCount.getValue();
    }


    private Map<String, Object> getMonthlyMap(Histogram perMonthAgg) {
        Map<String, Object> map = Maps.newHashMap();
        List<? extends Histogram.Bucket> buckets = perMonthAgg.getBuckets();
        for (Histogram.Bucket bucket : buckets) {
            String month = bucket.getKeyAsString();
            ParsedSum sum = bucket.getAggregations().get("total_profit");
            BigDecimal totalProfit = BigDecimalUtil.parseBigDecimal(sum.getValueAsString());
            map.put(month, totalProfit);
        }
        return map;
    }

    private List<KeyValueVO> getCaseDistributeList(Range rwRanges) {
        List<KeyValueVO> caseDistributeList = Lists.newArrayList();
        for (Range.Bucket entry : rwRanges.getBuckets()) {
            String key = entry.getKeyAsString();
            long docCount = ((ValueCount) entry.getAggregations().get("caseCount")).getValue();

            KeyValueVO keyValueVO = new KeyValueVO();
            keyValueVO.setKey(key);
            keyValueVO.setValue(docCount);
            caseDistributeList.add(keyValueVO);
        }
        return caseDistributeList;
    }

    private void setCaseDistributeList(Range rwRanges ,Map<String, List<Object>> dataMap, List<String> segmentList) {
        for (Range.Bucket entry : rwRanges.getBuckets()) {
            String key = entry.getKeyAsString();
            long docCount = ((ValueCount) entry.getAggregations().get("caseCount")).getValue();

            List<Object> datas = dataMap.get(key);
            if(CollectionUtils.isEmpty(datas)){
                datas = Lists.newArrayList();
            }
            datas.add(docCount);
            dataMap.put(key, datas);
            if(!segmentList.contains(key)){
                segmentList.add(key);
            }
        }

    }


}
